package com.example.testdemo.spring.factory;

import com.example.testdemo.expection.BeansException;
import com.example.testdemo.spring.ApplicationContext;
import com.example.testdemo.spring.bean.BeanDefinition;
import com.example.testdemo.spring.bean.GenericBeanDefinition;
import com.example.testdemo.spring.config.ApplicationContextAware;
import com.example.testdemo.spring.config.BeanFactoryPostProcessor;
import com.example.testdemo.spring.config.InitializingBean;
import com.example.testdemo.utils.StringUtils;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

/**
 * <p>
 *
 * </p>
 *
 * @author 永健
 * @since 2020-03-29 00:50
 */
public class DefaultBeanFactory implements SingletonBeanFactory {

    private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(1000);
    private final List<BeanFactoryPostProcessor> beanFactoryPostProcessor = new ArrayList<>(256);
    private final List<InitializingBean> initializingBean = new ArrayList<>(256);
    private final List<ApplicationContextAware> applicationContextAwares = new ArrayList<>(256);
    private final Map<Class<?>, Object> beanMap = new ConcurrentHashMap<>(1000);
    private final Map<String, Object> beanMapS = new ConcurrentHashMap<>(1000);
    private final Set<String> beanNames = new HashSet<>(1000);

    public DefaultBeanFactory() {

    }

    @Override
    public void registerSingleton(String beanName, BeanDefinition beanDefinition) throws BeansException {
        BeanDefinition definition = beanDefinitionMap.get(beanName);
        if (StringUtils.isNotNull(definition)) {
            throw new BeansException("bean name " + beanName + " is Already exist");
        }
        beanNames.add(beanName);
        beanDefinitionMap.put(beanName, beanDefinition);

        registerBeanFactoryPostProcessor(beanDefinition);
    }


    private void registerApplicationContextAwares(Object newInstance) {
        if (ApplicationContextAware.class.isAssignableFrom(newInstance.getClass())) {
            this.applicationContextAwares.add((ApplicationContextAware) newInstance);
        }
    }

    private void registerInitializingBean(Object newInstance) {
        if (InitializingBean.class.isAssignableFrom(newInstance.getClass())) {
            this.initializingBean.add((InitializingBean) newInstance);
        }
    }

    private void registerBeanFactoryPostProcessor(BeanDefinition beanDefinition) {

        GenericBeanDefinition definition = (GenericBeanDefinition) beanDefinition;

        if (BeanFactoryPostProcessor.class.isAssignableFrom(definition.getBeanClass())) {
            try {
                this.beanFactoryPostProcessor.add((BeanFactoryPostProcessor) definition.getBeanClass().newInstance());
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            }
        }
    }

    public List<ApplicationContextAware> getApplicationContextAwares() {
        return applicationContextAwares;
    }

    public List<BeanFactoryPostProcessor> getBeanFactoryPostProcessor() {
        return beanFactoryPostProcessor;
    }

    public List<InitializingBean> getInitializingBean() {
        return initializingBean;
    }

    public Map<String, BeanDefinition> getBeanDefinitionMap() {
        return beanDefinitionMap;
    }

    @Override
    public void registerSingleton(String beanName, Object obj) throws BeansException {
        Object o = beanMap.get(beanName);
        if (StringUtils.isNotNull(o)) {
            throw new BeansException("bean name " + beanName + " is Already exist");
        }
        beanMap.put(obj.getClass(), obj);
        beanMapS.put(beanName, obj);
        registerInitializingBean(obj);
        registerApplicationContextAwares(obj);
    }

    @Override
    public <T> T getBean(String beanName) throws BeansException {
        return (T) beanMapS.get(beanName);
    }

    @Override
    public Object getBean(Class<?> aClass) throws BeansException {
        return beanMap.get(aClass);
    }

    @Override
    public int getBeanDefinitionCount() {
        return beanDefinitionMap.size();
    }

    @Override
    public Set<String> getBeanDefinitionNames() {
        return beanNames;
    }
}
